import Redis from 'ioredis';
import { Types } from 'mongoose';
import {
  ScoringService,
  ScoringCache,
} from '../types/services';
import { config } from '../config/serviceConfig';
import { errorHandler, AppError } from '../utils/errorHandler';
import { EventEmitter } from 'events';

interface ScoreBreakdown {
  accuracy: number;
  completeness: number;
  relevance: number;
  clarity: number;
}

interface ScoringConfig {
  weights: {
    accuracy: number;
    completeness: number;
    relevance: number;
    clarity: number;
  };
  thresholds: {
    minimum: number;
    passing: number;
    excellent: number;
  };
  cache: {
    ttl: number;
    prefix: string;
  };
}

export class ScoringServiceImpl extends EventEmitter implements ScoringService {
  private static instance: ScoringServiceImpl;
  private readonly redis: Redis;
  private readonly cacheKeyPrefix: string;
  private readonly scoringConfig: ScoringConfig;
  private isShuttingDown = false;

  constructor(
    redis: Redis, 
    scoringConfig: ScoringConfig = config.scoring,
    cacheConfig: typeof config.performance.cache = config.performance.cache
  ) {
    super();
    this.redis = redis;
    this.scoringConfig = scoringConfig;
    this.cacheKeyPrefix = scoringConfig.cache.prefix;
    this.setupEventHandlers();
  }

  static getInstance(
    redis: Redis,
    scoringConfig?: ScoringConfig,
    cacheConfig?: typeof config.performance.cache
  ): ScoringServiceImpl {
    if (!ScoringServiceImpl.instance) {
      ScoringServiceImpl.instance = new ScoringServiceImpl(
        redis,
        scoringConfig || config.scoring,
        cacheConfig
      );
    }
    return ScoringServiceImpl.instance;
  }

  async healthCheck(): Promise<boolean> {
    try {
      if (this.isShuttingDown) {
        return false;
      }

      // 检查Redis连接
      await this.redis.ping();

      // 验证评分权重
      const weights = this.scoringConfig.weights;
      const weightSum = Object.values(weights).reduce((sum, weight) => sum + weight, 0);
      if (Math.abs(weightSum - 1) > 0.001) {
        throw new AppError('Invalid scoring weights configuration', 'CONFIG_ERROR');
      }

      // 验证阈值配置
      const { minimum, passing, excellent } = this.scoringConfig.thresholds;
      if (minimum > passing || passing > excellent) {
        throw new AppError('Invalid scoring thresholds configuration', 'CONFIG_ERROR');
      }

      return true;
    } catch (error) {
      errorHandler.handleError(error instanceof Error ? error : new Error('Health check failed'));
      return false;
    }
  }

  async getCachedScore(answerId: Types.ObjectId): Promise<ScoringCache | null> {
    try {
      if (this.isShuttingDown) {
        throw new AppError('Service is shutting down', 'SERVICE_SHUTDOWN');
      }

      const cacheKey = this.buildCacheKey(answerId);
      const cached = await this.redis.get(cacheKey);

      if (!cached) {
        return null;
      }

      const scoreData: ScoringCache = JSON.parse(cached);
      
      // 检查缓存是否过期
      if (new Date(scoreData.expiresAt) < new Date()) {
        await this.invalidateCache(cacheKey);
        return null;
      }

      // 更新访问统计
      const pipeline = this.redis.pipeline();
      pipeline.hincrby('scoring:stats', cacheKey, 1);
      pipeline.hset('scoring:lastAccess', cacheKey, new Date().toISOString());
      await pipeline.exec();

      this.emit('cache_hit', { answerId, score: scoreData.score });
      return scoreData;
    } catch (error) {
      errorHandler.handleError(error instanceof Error ? error : new Error('Failed to get cached score'));
      return null;
    }
  }

  async calculateScore(answerId: Types.ObjectId, answer: string): Promise<number> {
    try {
      if (this.isShuttingDown) {
        throw new AppError('Service is shutting down', 'SERVICE_SHUTDOWN');
      }

      const scores = await this.evaluateAnswer(answer);
      const weights = this.scoringConfig.weights;
      
      const totalScore = Object.entries(scores).reduce((sum, [aspect, score]) => {
        return sum + (score * weights[aspect as keyof typeof weights]);
      }, 0);

      const normalizedScore = Math.round(totalScore * 100);
      
      // 缓存结果
      await this.cacheScore(answerId, {
        answerId,
        score: normalizedScore,
        breakdown: scores,
        model: 'default',
        timestamp: new Date(),
        expiresAt: new Date(Date.now() + this.scoringConfig.cache.ttl * 1000)
      });

      this.emit('score_calculated', { answerId, score: normalizedScore });
      return normalizedScore;
    } catch (error) {
      errorHandler.handleError(error instanceof Error ? error : new Error('Score calculation failed'));
      throw error;
    }
  }

  async updateScoringConfig(updates: Partial<ScoringConfig>): Promise<void> {
    try {
      if (this.isShuttingDown) {
        throw new AppError('Service is shutting down', 'SERVICE_SHUTDOWN');
      }

      // 更新并验证配置
      const newConfig = {
        ...this.scoringConfig,
        ...updates,
        weights: {
          ...this.scoringConfig.weights,
          ...updates.weights
        }
      };

      const weightSum = Object.values(newConfig.weights).reduce((sum, weight) => sum + weight, 0);
      if (Math.abs(weightSum - 1) > 0.001) {
        throw new AppError('Invalid scoring weights configuration', 'CONFIG_ERROR');
      }

      Object.assign(this.scoringConfig, newConfig);

      // 清理所有缓存
      await this.clearAllCache();
      this.emit('config_updated', newConfig);
    } catch (error) {
      errorHandler.handleError(error instanceof Error ? error : new Error('Configuration update failed'));
      throw error;
    }
  }

  async cleanup(): Promise<void> {
    try {
      this.isShuttingDown = true;
      this.emit('stopping');

      // 清理缓存
      await this.clearAllCache();
      
      // 等待所有事件处理完成
      await new Promise(resolve => setImmediate(resolve));
      
      this.emit('stopped');
    } catch (error) {
      errorHandler.handleError(error instanceof Error ? error : new Error('Cleanup failed'));
    }
  }

  // 私有辅助方法
  private async evaluateAnswer(answer: string): Promise<ScoreBreakdown> {
    // TODO: 实现具体的评分逻辑，可能需要调用AI模型
    return {
      accuracy: 0.85,
      completeness: 0.9,
      relevance: 0.8,
      clarity: 0.95
    };
  }

  private async cacheScore(answerId: Types.ObjectId, scoreData: ScoringCache): Promise<void> {
    const cacheKey = this.buildCacheKey(answerId);
    const pipeline = this.redis.pipeline();
    
    pipeline.set(cacheKey, JSON.stringify(scoreData));
    pipeline.expire(cacheKey, this.scoringConfig.cache.ttl);
    
    await pipeline.exec();
  }

  private async invalidateCache(key: string): Promise<void> {
    const pipeline = this.redis.pipeline();
    pipeline.del(key);
    pipeline.hdel('scoring:stats', key);
    pipeline.hdel('scoring:lastAccess', key);
    await pipeline.exec();
  }

  private async clearAllCache(): Promise<void> {
    let cursor = '0';
    do {
      const [newCursor, keys] = await this.redis.scan(
        cursor,
        'MATCH',
        `${this.cacheKeyPrefix}*`,
        'COUNT',
        100
      );
      cursor = newCursor;

      if (keys.length > 0) {
        const pipeline = this.redis.pipeline();
        keys.forEach(key => {
          pipeline.del(key);
          pipeline.hdel('scoring:stats', key);
          pipeline.hdel('scoring:lastAccess', key);
        });
        await pipeline.exec();
      }
    } while (cursor !== '0');
  }

  private buildCacheKey(answerId: Types.ObjectId): string {
    return `${this.cacheKeyPrefix}${answerId.toString()}`;
  }

  private setupEventHandlers(): void {
    this.on('error', (error: Error) => {
      errorHandler.handleError(error);
    });

    // 监听缓存命中率
    this.on('cache_hit', ({ answerId }) => {
      void this.redis.hincrby('scoring:metrics', 'cache_hits', 1);
    });

    // 监听评分计算
    this.on('score_calculated', ({ answerId, score }) => {
      void this.redis.hincrby('scoring:metrics', 'calculations', 1);
      void this.redis.zadd('scoring:recent', Date.now(), JSON.stringify({ answerId, score }));
    });

    // 监听配置更新
    this.on('config_updated', (newConfig: ScoringConfig) => {
      void this.redis.hset('scoring:config', 'current', JSON.stringify(newConfig));
    });
  }
}
